package com.heisenhub.uua.config;

import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.heisenhub.uua.enhancer.CustomTokenEnhancer;
import com.heisenhub.uua.service.AbstractCustomTokenGranter;
import com.heisenhub.uua.service.impl.RedisClientDetailsService;
import com.heisenhub.uua.service.impl.UuaUserServiceImpl;
import com.heisenhub.uua.translator.HubWebResponseExceptionTranslator;
import com.heisenhub.uua.utils.ReflectionsUtils;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.CompositeTokenGranter;
import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.security.oauth2.provider.client.ClientCredentialsTokenGranter;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;

import javax.sql.DataSource;
import java.lang.reflect.Constructor;
import java.util.*;

/**
 * @author sen
 * @description
 * @date 2024/3/19 14:49
 */
@Configuration
@EnableAuthorizationServer
public class HubAuthorizationServerConfigure extends AuthorizationServerConfigurerAdapter {

    private static ArrayList<TokenGranter> granter = new ArrayList<>();

    @Autowired
    private AuthenticationManager authenticationManager;
//    @Autowired
//    private RedisConnectionFactory redisConnectionFactory;
    @Autowired
    private UuaUserServiceImpl uuaUserService;
//    @Autowired
//    private PasswordEncoder passwordEncoder;

    @Autowired
    private HubWebResponseExceptionTranslator exceptionTranslator;


    @Autowired
    private CustomTokenEnhancer customTokenEnhancer;

    @Autowired
    private RedisClientDetailsService redisClientDetailsService;

    /**
     * 多数据原的时候  使用
     */
    @Autowired
    private DynamicRoutingDataSource dynamicRoutingDataSource;


    @Value("mysql.dbname")
    private String dbname;

    /**
     * 客户端从认证服务器获取令牌的时候，必须使用client_id为heisen，client_secret为123456的标识来获取；
     * 该client_id支持password模式获取令牌，并且可以通过refresh_token来获取新的令牌；
     * 在获取client_id为sen的令牌的时候，scope只能指定为all，否则将获取失败；
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//        clients.inMemory()
//                .withClient("heisen")
//                .secret(passwordEncoder.encode("123456"))
//                .authorizedGrantTypes("password", "refresh_token")
//                .scopes("all");

        clients.withClientDetails(redisClientDetailsService);  // 先从redis 中读

    }


    /**
     * 登入时  如果不配置 Authorization    Basic aGVpc2VuOjEyMzQ1Ng==  请打开注释
     * eGlhb3NoaToxMjM0NTY=  是client_id:client_secret  加密得到的
     * @param security
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security)  {

        security
                .checkTokenAccess("isAuthenticated()") //客户端校验token访问许可 /oauth/check_key公开
                //客户端token调用许可 /oauth/check_token公开  // 获取密钥需要身份认证，使用单点登录时必须配置
                .tokenKeyAccess("permitAll()")
                //表单认证,申请令牌
                .allowFormAuthenticationForClients();
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints.tokenStore(tokenStore())
                .userDetailsService(uuaUserService)
                .authenticationManager(authenticationManager)  // 身份管理器
                .tokenEnhancer(customTokenEnhancer)  //令牌增强器
              //  .tokenServices(defaultTokenServices())
                .exceptionTranslator(exceptionTranslator);  // 翻译

        /**
         * 其他  登入方法
         */
        List<TokenGranter> tokenGranters = getTokenGranters(endpoints.getTokenServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory() ,authenticationManager);
        tokenGranters.add(endpoints.getTokenGranter());
        endpoints.exceptionTranslator(exceptionTranslator);
        endpoints.tokenGranter(new CompositeTokenGranter(tokenGranters));
    }



    /**
     * 其他  登入方法  请写在outh2 .login 包下
     *
     * @param tokenServices
     * @param clientDetailsService
     * @param requestFactory
     * @return
     */
    @SneakyThrows
    private List<TokenGranter> getTokenGranters(AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory ,AuthenticationManager authenticationManager) {

        Set<Class<? extends AbstractCustomTokenGranter>> subTypes = ReflectionsUtils.reflections.getSubTypesOf(AbstractCustomTokenGranter.class);
        for (Class<? extends AbstractCustomTokenGranter> subType : subTypes) {
            Constructor<? extends AbstractCustomTokenGranter> constructor = subType.getConstructor(AuthorizationServerTokenServices.class, ClientDetailsService.class, OAuth2RequestFactory.class, UserDetailsService.class,AuthenticationManager.class);
            AbstractCustomTokenGranter abstractCustomTokenGranter = constructor.newInstance(tokenServices, clientDetailsService, requestFactory, uuaUserService ,authenticationManager);
            granter.add(abstractCustomTokenGranter);
        }

        /**
         * 客户端登入
         */
        ClientCredentialsTokenGranter clientCredentialsTokenGranter = new ClientCredentialsTokenGranter(tokenServices, clientDetailsService, requestFactory);
        // 设置返回refresh code
        clientCredentialsTokenGranter.setAllowRefresh(true);
        granter.add(clientCredentialsTokenGranter);
        return granter;

//        return new ArrayList<>(Arrays.asList(
//                /** todo 新增的grant_type 添加到这里*/
//                new WebPcLoginGranter(tokenServices,clientDetailsService,requestFactory,uuaUserService,authenticationManager)
//
//
//        ));
    }


    /**
     *  token 来源
     * @return
     */
    @Bean
    public TokenStore tokenStore() {
      //  RedisTokenStore tokenStore = new RedisTokenStore(redisConnectionFactory);
        JdbcTokenStore tokenStore = new JdbcTokenStore(getDataSource()); //数据库
        // 解决每次生成的 token都一样的问题
      //  tokenStore.setAuthenticationKeyGenerator(oAuth2Authentication -> UUID.randomUUID().toString());
        return tokenStore;
    }

    @Primary
    @Bean
    public DefaultTokenServices defaultTokenServices() {
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(tokenStore());
        // 如果已经生成了一次没有自定义的token信息，需要去redis里删除掉该token才能再次测试结果，不然你的结果一直是错误的，因为token还没过期的话，是不会重新生成的。
        tokenServices.setTokenEnhancer(this.customTokenEnhancer); // 令牌增强器
        tokenServices.setSupportRefreshToken(true);  //设置为true表示开启刷新令牌的支持。
        tokenServices.setAccessTokenValiditySeconds(60 * 60 * 24);
        tokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 7);  //刷新令牌有效时间
        return tokenServices;
    }



    /**
     * 数据库 存储
     *
     * @return
     */
    public DataSource getDataSource() {
        return dynamicRoutingDataSource.getDataSource(dbname);
    }

    /**
     * 数据库 存储   clients
     *
     * @return
     */
    @Bean
    public ClientDetailsService jdbcClientDetailsService() {
        return new JdbcClientDetailsService(getDataSource());

    }


}

